﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;

using alib.Array;
using alib.Character;
using alib.Debugging;
using alib.Enumerable;
using alib.Collections;

using agree.rewriter;
using agree.rewriter.regex;
using agree.tmr;

namespace agree
{
	using SysRegex = System.Text.RegularExpressions.Regex;
	using SysMatch = System.Text.RegularExpressions.Match;
	using SysRegexOptions = System.Text.RegularExpressions.RegexOptions;
	using Element = tmr.Element;

	namespace tmr
	{
		[DebuggerDisplay("{ToString(),nq}")]
		[StructLayout(LayoutKind.Auto, Pack = 8)]
		public struct RegexCaptureRef
		{
			public RegexCaptureRef(TgtElem tgt, String trip, Flags f)
			{
				var rgs = trip.Split(Charset.colon);
				if (rgs.Length != 3)
					throw new Exception();

				this.flags = f;

				var tmr = tgt.tmr;
				var src_elem = tmr[new ModeIx(rgs[0])] as SrcElem;
				if (src_elem == null || !src_elem.HasRegex)
					throw new Exception();

				var fsp = new FsPath(tmr.im, rgs[1]);
				var src_regex = src_elem.FindRegex(fsp);
				this.match_index = (short)src_regex.match_index;

				this.cap_num = sbyte.Parse(rgs[2]);
				if (cap_num > src_regex.GetGroupNumbers().Length)
					throw new Exception();
				cap_num--;
			}

			public enum Flags : byte { None = 0, ConvertUpper = 1, ConvertLower = 2 };

			public short match_index;
			public sbyte cap_num;
			Flags flags;

			public String GetValue(SysMatch[] rgm)
			{
				String cap = rgm[match_index].Captures[cap_num].Value;
				switch (flags)
				{
					case Flags.ConvertLower:
						cap = cap.ToLowerInvariant();
						break;
					case Flags.ConvertUpper:
						cap = cap.ToUpperInvariant();
						break;
				}
				return cap;
			}

			public override String ToString()
			{
				return String.Format("ref cap{0}-match{1}", cap_num, match_index);
			}
		};

		[DebuggerDisplay("{ToString(),nq}")]
		public struct RegexReplace
		{
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			public static readonly RegexReplace[] None = Collection<RegexReplace>.None;
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			public static readonly String[] ReplaceTokenArr = new[] { TdlTok.ReplaceToken };

			public RegexReplace(TgtElem out_elem, FsPath fsp, String s_repl)
			{
				var rgs = s_repl.Split(ReplaceTokenArr, StringSplitOptions.None);
				this.fsp = fsp;
				this.caprefs = new RegexCaptureRef[rgs.Length - 1];
				this.format = "";

				if (caprefs.Length == 0)
					throw new Exception();

				format += rgs[0];
				for (int i = 1; i < rgs.Length; i++)
				{
					var rgs2 = rgs[i].Split(Charset.cl_brace);
					if (rgs2.Length != 2)
						throw new Exception();

					RegexCaptureRef.Flags f = RegexCaptureRef.Flags.None;
					String trip = rgs2[0];
					String tail = rgs2[1];

					if (trip.StartsWith("lc("))
					{
						f = RegexCaptureRef.Flags.ConvertLower;
						trip = trip.Substring(3, trip.Length - 4);
					}
					else if (trip.StartsWith("uc("))
					{
						f = RegexCaptureRef.Flags.ConvertUpper;
						trip = trip.Substring(3, trip.Length - 4);
					}

					caprefs[i - 1] = new RegexCaptureRef(out_elem, trip, f);

					if (tail.IndexOfAny(Charset.braces) != -1)
						throw new Exception();

					format += "{" + (i - 1).ToString() + "}" + tail;
				}
			}
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			public readonly FsPath fsp;
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			public readonly String format;
			[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
			public readonly RegexCaptureRef[] caprefs;

			Object[] resolve_captures(SysMatch[] rgm)
			{
				Object[] arr = new Object[caprefs.Length];
				for (int k = 0; k < caprefs.Length; k++)
					arr[k] = caprefs[k].GetValue(rgm);
				return arr;
			}

			public void Apply(TfsSlot ts_new, SysMatch[] rgm)
			{
				int ix1 = ts_new[fsp].ix1;
				if (ix1 <= 0)
					throw new Exception();

				var result = String.Format(format, resolve_captures(rgm));

				alter_tfs(ref ts_new.tfs.entries[ix1 - 1], result);
			}

			/// note: assumes that the value of the (out-) edge does not affect the hashing of the entry
			void alter_tfs(ref arr_tfs_entry ent, String result)
			{
				if (ent.e_FlagsId < 0 || ent.e_Mark < 0)
					throw new Exception();
				ent.e = fsp.im.tu.CreateStringEdge(result);
			}

			public override String ToString()
			{
				//return String.Format(" ...{0,-18} {1} {2}",
				//	fsp.ToString(),
				//	format,
				//	caprefs.Select(cr => "[" + cr.ToString() + "]").StringJoin(" "));
				return String.Format("  ...{0,-18} |{1}|",
					fsp.ToString(),
					String.Format(format, caprefs.Select(x => "[" + x.ToString() + "]" as Object).ToArray()));
			}
		};


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		[DebuggerDisplay("{ToString(),nq}", Name = "{mode_ix.ToString(),nq}")]
		//[DebuggerDisplay("{tmr.Name,nq} {mode_ix.ToString(),nq}")]
		public abstract class Element : rewriter.Element
		{
			public Element(TokenMappingRule tmr, ModeIx mix, TfsSlot ts_corner, int ci_index)
				: base(ts_corner, mix, ci_index)
			{
				this.tmr = tmr;
			}
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			public readonly TokenMappingRule tmr;

			[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
			public TfsSlot _ts_debug { get { return base.TfsSlot; } }
		};


		[DebuggerDisplay("{ToString(),nq}", Name = "{mode_ix.ToString(),nq}")]
		//[DebuggerDisplay("{tmr.Name,nq} {mode_ix.ToString(),nq}")]
		public sealed class SrcElem : Element, IReadOnlyCollection<Regex>
		{
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			public static readonly SrcElem[] None = Collection<SrcElem>.None;

			public SrcElem(TokenMappingRule tmr, ModeIx mix, TfsSlot ts_corner, int ci_index)
				: base(tmr, mix, ts_corner, ci_index)
			{
				var L = tmr.match_raw;
				if (L != null)
				{
					int i = 0;
					while (i < L.Count)
					{
						var rx = L[i];
						if (rx.mode_ix != mix)
							i++;
						else
						{
							rx.match_index = tmr.RegexCount++;
							arr.Append(ref rgrx, rx);
							L.RemoveAt(i);
						}
					}
				}

				if (!HasRegex)
					rgrx = Regex.None;
			}

			///////////////////////////////////////////////////////////////////////
			///
			Regex[] rgrx;
			public bool HasRegex { get { return Count > 0; } }
			public int Count { get { return rgrx == null ? 0 : rgrx.Length; } }
			public IEnumerator<Regex> GetEnumerator() { return rgrx.Enumerator(); }
			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); }
			///
			///////////////////////////////////////////////////////////////////////

			public Regex FindRegex(FsPath fsp)
			{
				Regex a;
				if (HasRegex)
					for (int i = 0; i < rgrx.Length; i++)
						if ((a = rgrx[i]).fsp.Equals(fsp))
							return a;
				return null;
			}

			public bool MatchElement(ITfsSlot ts_input, ref SysMatch[] rgm)
			{
				if (HasRegex)
				{
					for (int j = 0; j < rgrx.Length; j++)
					{
						var a = rgrx[j];

						var s_hyp = a.fsp[ts_input].StringValue;
						if (s_hyp == null)
							return false;
#if false
						if (q != null && q.ToLower() == "byfjellene." && tmr.Name == "downcase_tmr")
							Nop.X();

						var ef = a.fsp.GetEdgeFlag(ts_input);
						if (ef < 0)
							return false;

						if (ef.IsTheStringType())
							return false;

						if (!tu.IsStringValue(ef))
							return false;

						var s_hyp = tu.GetStringValue(ef);
#endif

						var m = a.Match(s_hyp);
						if (!m.Success)
							return false;

						/// DELPH-IN token mapping rules (namely, ditch_punctuation_tmr) interpret
						/// ^a|b$ as ^(a|b)$ as opposed to ^a.*|.*b$. Thus "xb" should not result 
						/// in a match 'b'. We can effect this by discarding this match if it does 
						/// not bind the full input string.
						if (m.Groups[0].Value != s_hyp)
							return false;

						if (rgm == null)
							rgm = new SysMatch[tmr.RegexCount];
						else
							Debug.Assert(rgm[a.match_index] == null);
						rgm[a.match_index] = m;
					}
				}
				return true;
			}

			public override String ToString()
			{
				var s = base.ToString();
				if (HasRegex)
				{
					s += " " + Environment.NewLine;
					s += rgrx.Select(a => String.Format("\t[{0}]", a.ToString())).StringJoin(" " + Environment.NewLine);
				}
				return s;
			}
		};

		//[DebuggerDisplay("{ToString(),nq}", Name = "{mode_ix.ToString(),nq}")]
		[DebuggerDisplay("{tmr.Name,nq} {mode_ix.ToString(),nq}")]
		public sealed class TgtElem : Element
		{
			[DebuggerBrowsable(DebuggerBrowsableState.Never)]
			public static TgtElem[] None = Collection<TgtElem>.None;

			public TgtElem(TokenMappingRule tmr, ModeIx mix, TfsSlot ts_corner)
				: base(tmr, mix, ts_corner, -1)
			{
				replacements = RegexReplace.None;
			}

			[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
			public RegexReplace[] replacements;

			public void Apply(TfsSlot ts_new, SysMatch[] rgm)
			{
				for (int j = 0; j < replacements.Length; j++)
					replacements[j].Apply(ts_new, rgm);
			}

			public override String ToString()
			{
				String s = base.ToString();
				if (replacements.Length > 0)
				{
					s += " " + Environment.NewLine;
					s += replacements.Select(a => String.Format("\t[{0}]", a.ToString())).StringJoin(" " + Environment.NewLine);
				}
				return s;
			}
		};

	}//agree.tmr

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[DebuggerDisplay("{ToString(),nq}")]
	public sealed partial class TokenMappingRule : RegexRewriterRule<tmr.SrcElem, tmr.TgtElem>
	{
		public TokenMappingRule(Grammar g, Type t, ConsDefs consdef)
			: base(g, t, consdef)
		{
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		int ifeat_output;

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public int CaptureRefsCount
		{
			get
			{
				if (Output == null)
					return 0;
				return Output.Sum(tgt => tgt.replacements.Sum(rr => rr.caprefs.Length));
			}
		}

		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		public RegexCaptureRef[] CaptureRefs
		{
			get { return Output.SelectMany(tgt => tgt.replacements.SelectMany(rr => rr.caprefs)).ToArray(); }
		}

		protected override void AnalyzeRuleBehavior(Tfs tfs)
		{
			base.AnalyzeRuleBehavior(tfs);

			var tmcfg = em.TokenMappingGeometry;
			int ifeat_context = tmcfg.ifeat_plus_context;
			int ifeat_input = tmcfg.ifeat_plus_input;
			this.ifeat_output = tmcfg.ifeat_plus_output;

			/// load context-sensitive processing info
			int ci_index = 0;

			Context = prepare_src_toks(tfs[ifeat_context], Element.Mode.CONTEXT, ref ci_index);
			Input = prepare_src_toks(tfs[ifeat_input], Element.Mode.INPUT, ref ci_index);
			Output = prepare_tgt_toks(tfs[ifeat_output], Element.Mode.OUTPUT);

			if (!HasRegex || Output.Length == 0)
			{
				/// cannot have replacement text if there are no regexes or output elements
				Debug.Assert(repl_raw == null);
			}
			else
			{
				/// all regexes must be claimed and accounted for
				Debug.Assert(match_raw.Count == 0);
				match_raw = null;
			}

			if (repl_raw != null)
			{
				foreach (var repl in repl_raw)
				{
					var out_elem = (TgtElem)this[repl.mode_ix];

					var csrr = new RegexReplace(out_elem, repl.fsp, repl.s_repl);

					alib.Array.arr.Append(ref out_elem.replacements, csrr);
				}
				repl_raw = null;
			}
		}

		SrcElem[] prepare_src_toks(TfsSlot ts, Element.Mode mode, ref int ci_index)
		{
			TfsSlot[] ls = ts.ListSlots;
			if (ls.Length == 0)
				return SrcElem.None;

			var arr = new SrcElem[ls.Length];
			for (int i = 0; i < ls.Length; i++)
				arr[i] = new SrcElem(this, new ModeIx(mode, i), ls[i], ci_index++);
			return arr;
		}

		TgtElem[] prepare_tgt_toks(TfsSlot ts, Element.Mode mode)
		{
			TfsSlot[] ls = ts.ListSlots;
			if (ls.Length == 0)
				return TgtElem.None;

			var arr = new TgtElem[ls.Length];
			for (int i = 0; i < ls.Length; i++)
				arr[i] = new TgtElem(this, new ModeIx(mode, i), ls[i]);
			return arr;
		}

		/// <summary>
		/// Try to match each of the +CONTEXT and +INPUT elements of this rule to the elements
		/// provided. The list must have the correct number of elements and must be ordered
		/// according to this rule's elements' CsrElement.Index value. Returns null if this
		/// rule is not fully satisfied. Otherwise, returns an object which can later be
		/// provided to this rule's 'Apply()' function to burn the results of the rule
		/// application into a TFS.
		/// </summary>
		public SysMatch[] MatchElements(TokenMappingLattice.Hypothesis hyp)
		{
			if (hyp.Count != ContextInput.Length)
				throw new Exception();

#if rewriter_debugging
			hyp.__matches = null;
#endif

			SysMatch[] rgm = null;
			var ret = ContextInput.All((src_el, ix) =>
				{
					var b = src_el.MatchElement(hyp[ix].data, ref rgm);
#if rewriter_debugging
					if (b)
					{
						foreach (var rx in src_el.Where(x => rgm[x.match_index] != null))
						{
							var tgttxt = hyp[src_el.Index].data[rx.fsp].StringValue;
							var pat = rx.Pattern;
#if false
							pat = pat
								.Replace(@"\p{L}", "[:alpha:]")
								.Replace(@"\p{P}\p{S}", "[:punct:]")
								.Replace(@"\p{Lu}", "[:upper:]")
								.Replace(@"\p{Ll}", "[:lower:]")
								.Replace(@"\p{Nd}", "[:digit:]")
								.Replace(@"\p{L}\p{Nd}", "[:alnum:]")
								.TrimStart('^')
								.TrimEnd('$');
#endif
							Debug.Print("{0} {1}", rx.fsp, tgttxt);
							TokenMapper.tw.WriteLine("[cm] regex_match(/{0}/, \"{1}\")", pat, tgttxt);
						}

					}
#endif
					return b;
				}) ? rgm : null;
#if rewriter_debugging
#warning cleanup debugging code
			hyp.__matches = rgm;
#endif

			return ret;
		}

		/// <summary>
		/// Applies the results of a previously obtained satisfaction of this rule to the 
		/// +OUTPUT elements (which must pre-exist in the number expected by this rule) in 
		/// the specified rule-like TFS target. See 'MatchElements' for details.
		/// </summary>
		public void Apply(Tfs tfs, SysMatch[] rgm)
		{
			TfsSlot[] ls;
			int ix1;
			if ((ls = tfs[ifeat_output].ListSlots).Length != Output.Length)
				throw new Exception();

			for (int i = 0; i < Output.Length; i++)
			{
				RegexReplace[] rgr = Output[i].replacements;
				for (int j = 0; j < rgr.Length; j++)
					rgr[j].Apply(ls[i], rgm);
			}
		}

		public override String ToString()
		{
			int cc = CaptureRefsCount;
			return String.Format("{0} {1}", base.ToString(), cc > 0 ? "P" + cc.ToString() : "  ");
		}

		[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
		Element[] _for_debug
		{
			get { return ContextInputOutput.OfType<Element>().ToArray(); }
		}
	};
}
